package com.ctsi.oauth.config.exception;

import com.ctsi.framework.common.exception.CoreErrorConstant;
import com.ctsi.framework.common.response.UnifiedResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.*;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.www.NonceExpiredException;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import javax.servlet.ServletException;
import javax.validation.constraints.NotNull;
import java.io.IOException;

/**
 * @author: zhangjw
 * @Date: 2018/9/17 10:52
 * @Description: 自定义异常转换
 */
public class CustomWebReponseExceptionTranslator implements WebResponseExceptionTranslator {

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    // TODO: 细化异常处理
    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
        if (ase != null) {
            if (ase instanceof ClientAuthenticationException) {
                if (ase instanceof InvalidGrantException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                } else if (ase instanceof UnauthorizedClientException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                } else if (ase instanceof RedirectMismatchException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                } else if (ase instanceof InvalidTokenException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                } else if (ase instanceof InvalidRequestException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                } else if (ase instanceof InvalidClientException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                } else if (ase instanceof BadClientCredentialsException) {
                    return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
                }
            } else if (ase instanceof OAuth2AccessDeniedException) {
                return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
            }else if (ase instanceof UnauthorizedUserException) {
                return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
            }else if (ase instanceof UnsupportedGrantTypeException) {
                return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
            }else if (ase instanceof UnsupportedResponseTypeException) {
                return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
            }else if (ase instanceof UserDeniedAuthorizationException) {
                return new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, ase.getMessage()), HttpStatus.OK);
            }
            return handleOAuth2Exception((OAuth2Exception) ase);
        }
        //认证异常
        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
        if (ase != null) {
            if (ase instanceof InsufficientAuthenticationException) {

            } else if (ase instanceof AccountStatusException) {
                // TODO：细化异常处理 自定义得NeedEnableException

            } else if (ase instanceof AuthenticationCredentialsNotFoundException) {

            } else if (ase instanceof AuthenticationServiceException) {

            } else if (ase instanceof BadCredentialsException) {

            } else if (ase instanceof RememberMeAuthenticationException) {

            } else if (ase instanceof CredentialsExpiredException) {

            } else if (ase instanceof NonceExpiredException) {

            } else if (ase instanceof PreAuthenticatedCredentialsNotFoundException) {

            } else if (ase instanceof ProviderNotFoundException) {

            } else if (ase instanceof SessionAuthenticationException) {

            } else if (ase instanceof UsernameNotFoundException) {

            }
            return handleOAuth2Exception(new UnauthorizedException(e.getMessage(), CoreErrorConstant.HEADER_TOKEN_ILLEGAL, e));
        }
        //授权异常
        ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (ase instanceof AccessDeniedException) {
            if (ase instanceof AuthorizationServiceException) {
//                logger.error("Authorization Service occur error: {} ",accessDeniedException.getMessage());
//                ur = UnifiedResponse.error(CoreErrorConstant.ERROR_CODE,exceptionFactory.getResource(CoreErrorConstant.ERROR_CODE));
            } else if (ase instanceof CsrfException || ase instanceof org.springframework.security.web.server.csrf.CsrfException) {
                // TODO：暂时不处理csrf异常
//                logger.error("CSRF occur error: {} ",accessDeniedException.getMessage());
//                ur = UnifiedResponse.error(CoreErrorConstant.ERROR_CODE,exceptionFactory.getResource(CoreErrorConstant.ERROR_CODE));
            }
            return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), CoreErrorConstant.PERMISSION_DENIED_ERROR, ase));
        }
        ase = (ServletException) throwableAnalyzer.getFirstThrowableOfType(ServletException.class, causeChain);
        if (ase != null) {
            if (ase instanceof HttpRequestMethodNotSupportedException) {
                return handleOAuth2Exception(new MethodNotAllowed(ase.getMessage(), CoreErrorConstant.COMMON_HTTPMETHOD_NOT_SUPPROT, ase));
            }
        }

        return handleOAuth2Exception(new ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), CoreErrorConstant.ERROR_CODE, e));
    }

    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {

        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }
        ResponseEntity<OAuth2Exception> response = new ResponseEntity(UnifiedResponse.error(CoreErrorConstant.USER_OR_PASSWORD_ERROR, e.getMessage()), HttpStatus.OK);
        return response;

    }

    public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
        this.throwableAnalyzer = throwableAnalyzer;
    }

    private static class BaseOAuth2Exception extends OAuth2Exception {
        private final String code;

        public BaseOAuth2Exception(String msg, Throwable t, @NotNull String code) {
            super(msg, t);
            this.code = code;
        }

        public String getCode() {
            return code;
        }
    }

    @SuppressWarnings("serial")
    private static class ForbiddenException extends BaseOAuth2Exception {

        public ForbiddenException(String msg, String code, Throwable t) {
            super(msg, t, code);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "access_denied";
        }

        @Override
        public int getHttpErrorCode() {
            return 403;
        }

    }

    @SuppressWarnings("serial")
    private static class ServerErrorException extends BaseOAuth2Exception {

        public ServerErrorException(String msg, String code, Throwable t) {
            super(msg, t, code);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "server_error";
        }

        @Override
        public int getHttpErrorCode() {
            return 500;
        }

    }

    @SuppressWarnings("serial")
    private static class UnauthorizedException extends BaseOAuth2Exception {

        public UnauthorizedException(String msg, String code, Throwable t) {
            super(msg, t, code);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "unauthorized";
        }

        @Override
        public int getHttpErrorCode() {
            return 401;
        }

    }

    @SuppressWarnings("serial")
    private static class MethodNotAllowed extends BaseOAuth2Exception {

        private final String code;

        public MethodNotAllowed(String msg, String code, Throwable t) {
            super(msg, t, code);
            this.code = code;
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "method_not_allowed";
        }

        @Override
        public int getHttpErrorCode() {
            return 405;
        }


    }
}
